package io.kiki.stack.http.feign.SpringMvcContract.encoding;

import io.kiki.stack.http.feign.RequestTemplate;

import java.util.Collection;
import java.util.Map;

/**
 * Enables the HTTP request payload compression by specifying the {@code Content-Encoding}
 * headers.
 */
public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor {

    /**
     * Creates new instance of {@link FeignContentGzipEncodingInterceptor}.
     *
     * @param properties the encoding properties
     */
    protected FeignContentGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
        super(properties);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void apply(RequestTemplate requestTemplate) {

        if (requiresCompression(requestTemplate)) {
            addHeader(requestTemplate, HttpEncoding.CONTENT_ENCODING_HEADER, getContentEncodings());
        }
    }

    private String[] getContentEncodings() {
        if (getFeignClientEncodingProperties().getContentEncodingTypes() == null || getFeignClientEncodingProperties().getContentEncodingTypes().length == 0) {

            throw new IllegalStateException("Invalid ContentEncodingTypes configuration");
        }

        return getFeignClientEncodingProperties().getContentEncodingTypes();
    }

    /**
     * Returns whether the request requires GZIP compression.
     *
     * @param requestTemplate the request template
     * @return true if request requires compression, false otherwise
     */
    private boolean requiresCompression(RequestTemplate requestTemplate) {

        final Map<String, Collection<String>> headers = requestTemplate.headers();
        return matchesMimeType(headers.get(HttpEncoding.CONTENT_TYPE)) && contentLengthExceedThreshold(headers.get(HttpEncoding.CONTENT_LENGTH));
    }

    /**
     * Returns whether the request content length exceed configured minimum size.
     *
     * @param contentLength the content length header value
     * @return true if length is grater than minimum size, false otherwise
     */
    private boolean contentLengthExceedThreshold(Collection<String> contentLength) {

        try {
            if (contentLength == null || contentLength.size() != 1) {
                return false;
            }

            final String strLen = contentLength.iterator().next();
            final long length = Long.parseLong(strLen);
            return length > getFeignClientEncodingProperties().getMinRequestSize();
        } catch (NumberFormatException ex) {
            return false;
        }
    }

    /**
     * Returns whether the content mime types matches the configured mime types.
     *
     * @param contentTypes the content types
     * @return true if any specified content type matches the request content types
     */
    private boolean matchesMimeType(Collection<String> contentTypes) {
        if (contentTypes == null || contentTypes.size() == 0) {
            return false;
        }

        if (getFeignClientEncodingProperties().getMimeTypes() == null || getFeignClientEncodingProperties().getMimeTypes().length == 0) {
            // no specific mime types has been set - matching everything
            return true;
        }

        for (String mimeType : getFeignClientEncodingProperties().getMimeTypes()) {
            if (contentTypes.contains(mimeType)) {
                return true;
            }
        }

        return false;
    }

}
